package game.control;

import game.model.GameModels;
import game.model.Position;
import game.view.GameOverWindow;

import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.*;

/**
 * 游戏控制器
 *
 * @see GameModels
 * @see GameOverWindow
 */
public class GameController {

    /**
     * 构造函数
     *
     * @param models 被控制的游戏模型
     * @param random 随机数
     */
    public GameController(GameModels models, Random random) {
        this.models = models;
        this.random = random;
    }

    private final GameModels models; // 被控制的游戏模型
    private final Random random; // 随机数

    /**
     * x 轴向右
     * y 轴向下
     */
    private static final Map<Integer, Integer> getDirectionByKeyCode = Map.of(KeyEvent.VK_D, 0, KeyEvent.VK_S, 1, KeyEvent.VK_A, 2, KeyEvent.VK_W, 3, KeyEvent.VK_RIGHT, 0, KeyEvent.VK_DOWN, 1, KeyEvent.VK_LEFT, 2, KeyEvent.VK_UP, 3);
    private static final Map<Integer, Position> diffPosition = Map.of(0, new Position(1, 0), 1, new Position(0, 1), 2, new Position(-1, 0), 3, new Position(0, -1));


    /**
     * 初始化游戏进程
     */
    public void initGame() {
        models.snakeDirection = random.nextInt(4);
        models.snake.addFirst(new Position(random.nextInt(models.MAP_WIDTH), random.nextInt(models.MAP_HEIGHT)));
        models.food = makeFoodPosition();
//        while (models.snake.contains(food)) food = randomPosition();
    }


    /**
     * @return 生成食物的坐标
     */
    private Position makeFoodPosition() {
        List<Position> candidate = new ArrayList<>();
        for (int x = 0; x < models.MAP_WIDTH; ++x) {
            for (int y = 0; y < models.MAP_HEIGHT; ++y) {
                candidate.add(new Position(x, y));
            }
        }
        candidate.removeAll(models.snake);
        return candidate.get(random.nextInt(candidate.size()));
    }

    /**
     * 通过键盘输入，更新游戏进程
     *
     * @param keyCode 键盘输入
     * @return 画面是否需要更新
     */
    public boolean updateGameByKeyCode(int keyCode) {
        int direction = getDirectionByKeyCode.get(keyCode);
        if (direction == models.snakeDirection) return false; // 如果是前进方向，那么不做处理
        return updateGameByDirection(direction);
    }

    /**
     * 计时器触发，更新游戏进程
     *
     * @return 画面是否需要更新
     */
    public boolean timerTriggered() {
        return updateGameByDirection(models.snakeDirection);
    }


    /**
     * 更新游戏进程
     *
     * @param direction 用户输入
     * @return 是否需要重新绘制画面
     */
    private boolean updateGameByDirection(int direction) {
        Position nextPosition = getNextPosition(direction);
        if (isIllegalDirection(direction)) return false; // 非法方向，不做任何事情
        if (!isInside(nextPosition) || models.snake.contains(nextPosition)) { // 游戏结束
            models.gameOver = true;
            JFrame gameOver = new GameOverWindow();
            gameOver.setVisible(true);
            return false;
        }
        if (Objects.equals(nextPosition, models.food)) { // 吃食物
            models.snakeDirection = direction;
            models.snake.addFirst(nextPosition);
            models.food = makeFoodPosition();
            models.score++;
        } else { // 不吃食物
            models.snakeDirection = direction;
            models.snake.addFirst(nextPosition);
            models.snake.pollLast();
        }
//        System.out.println(models.snake.getFirst());
        return true;
    }

    /**
     * 判断指定方向是否是非法的
     *
     * @param dir 指定方向
     * @return 是否是非法的
     */
    private boolean isIllegalDirection(int dir) {
        int diff = (dir - models.snakeDirection + 4) % 4;
        return diff == 2; // 反向
    }


    /**
     * 按指定方向移动蛇，返回移动后的坐标
     *
     * @param dir 指定方向
     * @return 移动后的坐标
     */
    private Position getNextPosition(int dir) {
        Position nextPos = models.snake.getFirst().clone();
        nextPos.x += diffPosition.get(dir).x;
        nextPos.y += diffPosition.get(dir).y;
        return nextPos;
    }

    /**
     * 判断指定坐标是否在地图内
     *
     * @param pos 指定坐标
     * @return 是否在地图内
     */
    private boolean isInside(Position pos) {
        return 0 <= pos.x && pos.x < models.MAP_WIDTH && 0 <= pos.y && pos.y < models.MAP_HEIGHT;
    }


}
